package shared;

import java.io.InputStream;
import java.io.OutputStream;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;

import javassist.NotFoundException;

import org.w3c.dom.Document;
import org.w3c.dom.Node;

public class XMLRMIRequestManager {
	
	private InputStream is;
	
	private OutputStream os;
	
	private Object service;
	
	public XMLRMIRequestManager(InputStream is, OutputStream os, Object service){
		this.is = is;
		this.os = os;
		this.service = service;
	}
	
	
	public void start() throws XMLRMIException{
		Document domResponse;
		try{
			Document domCall = receiveCall();
			domResponse = treat(domCall);
		}catch(XMLRMIException e){
			//TODO Voir le code erreur
			e.printStackTrace();
			domResponse = getResponseToSend(e.getMessage(), 1); 
		}
		sendResponse(domResponse);
	}
	
	
	private Document receiveCall() throws XMLRMIException{
		return XMLRMIDocumentTools.receiveDocument(is);
	}
	
	private void sendResponse(Document document) throws XMLRMIException{
		XMLRMIDocumentTools.sendDocument(document, os);
	}

	
	private Document treat(Document document) throws XMLRMIException{

		//Get the methods for the call
		Method[] methods = XMLRMIServicesTools.findMethodToCall(service.getClass(), document);
		
		System.out.println("On a trouve : "+methods.length+" methode(s)");
		
		//Try to invoke all methods, until one is working
		Object returnn = null;
		Object[] parameters = null;
		
		Node callNode = XMLRMIDocumentTools.getCallNodeFromDocument(document);
		int nbParameter = XMLRMIDocumentTools.getNbParamFromCall(callNode);
		
		for(Method method : methods){
			
			try{
				//We have to create the parameters
				if(nbParameter>0)
					parameters = tryCreateParameters(document, method.getParameterTypes());
				
				if(parameters == null)
					System.out.println("FUCK YOU PARAMETERS");
				
				returnn = tryInvoke(method, parameters);
				
				return getResponseToSend(returnn, parameters, method.getReturnType());
				
			}catch(InvocationTargetException e){
				//TODO Voir si c'est celle la pour les mauvais arguments, on test une autre
				//e.printStackTrace();
			} catch (NotFoundException e) {
				//TODO Erreur lors de l'essai de cette methode, la classe type est introuvable pour un parametre
				//e.printStackTrace();
			}
			
		}

		throw new XMLRMIException("Impossible to invoke any method of the service");
	}
	
	private Document getResponseToSend(Object returnn, Object[] parameters, Class<?> typeRetour) throws XMLRMIException{
		
		
		Document domResponse = XMLRMIDocumentTools.createDocument();
		
		//TODO Mettre en static le manager et faire une surClass pour gerer les oid
		XMLRMIManager manage = new XMLRMIManager();
		
		Node methodResponseNode = domResponse.createElement("methodResponse");
		Node paramsNode = domResponse.createElement("params");
		Node paramReturnNode = domResponse.createElement("param");
		Node returnNode = null;
		
		if(typeRetour.getSimpleName()=="void"){
			returnNode = domResponse.createElement("value");
			returnNode.setTextContent("void");
		}
		else{
			//toXML sur the object
			returnNode = manage.convert(returnn, domResponse, true);
		}
		
		paramReturnNode.appendChild(returnNode);
		
		for(Object parameter : parameters){
			Node paramNode = domResponse.createElement("param");
			Node valueNode = manage.convert(parameter, domResponse, true);
			paramNode.appendChild(valueNode);
			paramsNode.appendChild(paramNode);
		}
		
		methodResponseNode.appendChild(paramsNode);
		
		domResponse.appendChild(methodResponseNode);
		
		return domResponse;
	}
	
	private Document getResponseToSend(String message, int code) throws XMLRMIException{
		//TODO
		return XMLRMIDocumentTools.getDocumentFromString(getAStringError(message, code));
	}
	
	private String getAStringError(String message, int code){
		
		//TODO String puis DOM puis String puis Socket Gneuuh ?
		
		String xml = "";
		xml+= "<methodResponse>" +
				"<fault>" +
					"<value>" +
					"<struct>" +
						"<member>" +
							"<name>faultCode</name>" +
							"<value>"+code+"</value>" +
						"</member>" +
						"<member>" +
							"<name>faultString</name>" +
							"<value>"+message+"</value>" +
						"</member>" +
					"</struct>" +
					"</value>" +
				"</fault>" +
			"</methodResponse>";
		return xml;
	}
	
	private Object tryInvoke(Method method, Object[] parameters) throws XMLRMIException, InvocationTargetException{
		
		System.out.println("On tente d'invoquer une methode");
		Object returnn = null;
		try {
			returnn = method.invoke(service, parameters);
		} catch (IllegalAccessException e) {
			throw new XMLRMIException("IllegalAccessException :"+e.getMessage());
		}
		return returnn;
	}
	
	private Object[] tryCreateParameters(Document document, Class<?>[] types) throws XMLRMIException, NotFoundException{
		//TODO change to not re-create existing object created
		
		System.out.println("On tente de creer les "+ types.length +" parametre(s)");
		
		Node callNode = XMLRMIDocumentTools.getCallNodeFromDocument(document);
		Object[] returnn = new Object[types.length];
		
		for(int i=0; i < types.length; i++){
			Node paramNode = XMLRMIDocumentTools.getIParamNodeFromCall(callNode,i);
			Node valueNode = XMLRMIDocumentTools.getValueNodeFromParam(paramNode);
			returnn[i] = XMLRMIObjectTools.createObject(valueNode, types[i]);		
		}
		
		return returnn;
	}
}
